[ตอนที่ 2] เวิร์คช้อป CMS อย่างง่าย เก็บข้อมูลลงฐานข้อมูล
ในเวิร์คช้อบนี้ defaultController หรือ Index\Index\Controller จะมีความแตกต่างจากเวิร์คช้อปก่อนหน้าเพียงนิดเดียวในส่วนของการเรียก Controller ถัดไป
บรรทัดแรก เราจะกำหนดให้หน้าเพจที่เราต้องการเป็นชื่อคลาสเลย เช่น มีการเรียกหน้า home จะได้ชื่อคลาสเป็น Home\Index\Controller และให้ตรวจสอบว่ามีคลาสนี้ติดตั้งไว้หรือไม่ ถ้ามีจะไปโหลดคลาสที่พบ ซึ่งปกติแล้วคลาสที่พบจะเป็นโมดูลอื่นๆ เช่น Chat\Index\Controller ซึ่งก็คือ Controller หลักของโมดูล Chat (modules/chat/controllers/index.php) ส่วนในกรณีที่ไม่พบโมดูล ก็อาจจะเป็นหน้าเพจ ซึ่งจะถูกส่งต่อไปยัง Index\Main\Controller ที่บรรทัดถัดไปแทน
ส่วน Index\Main\Controller ก็แทบไม่มีอะไรแตกต่างเช่นกัน มันยังคงทำหน้าที่คล้ายๆเวิร์คช้อปก่อนหน้า
โดยการนำค่าโมดูลที่ส่งมา ไปตรวจสอบกับฐานข้อมูล ที่ Index\Module\Model ถ้าพบก็คืนค่าข้อมูลหน้าที่พบ ถ้าไม่พบก็จะคืนค่าข้อมูลจาก Index\Pagenotfound\View แทน
ในเวิร์คช้อปนี้จะใช้การอ่านข้อมูลจากฐานข้อมูลนะครับ ดังนั้น ที่ Index\Module\Model ก็จะทำหน้าที่เชื่อมต่อกับฐานข้อมูลเพื่ออ่านข้อมูล $module ที่เลือก ออกมา
อธิบายโค้ดเพิ่มเติมเป็นส่วนๆ นะครับ
โค้ดด้านบนเป็นการเรียกใช้งาน Model ครับ คีย์เวิร์ด static เป็นคำสั่งของ PHP หมายถึงคลาสนี้ หรือก็คือ \kotchasan\Model นั่นเอง (เขียนได้อีกแบบว่า $model = new \Kotchasan\Model;)
โค้ดด้านบนเป็นคำสั่งของ Model ใช้สำหรับสร้าง SQL Query มีชื่อว่า QueryBuilder (ถ้ามีการใช้คำสั่ง createQuery() แสดงว่ามีการใช้ QueryBuilder) รูปแบบของคำสั่งจะมีลักษณะคล้ายๆกับคำสั่งของ SQL ปกติ เพื่อให้จดจำได้ง่าย ซึ่งการดูผลลัพท์ SQL ของ Query ที่สร้างสามารถดูได้จากคำสั่ง text();
คำสั่งด้านบนคือตัวอย่างการใช้งาน text() พร้อมผลลัพท์ SQL ที่ได้
คำสั่ง cacheOn(); เป็นคำสั่งเปิดการใช้งานแคชของ QueryBuilder ถ้าต้องการเปิดใช้งานแคชจะต้องมีการดำเนินการดังนี้
สุดท้ายคือคำสั่ง query เอาผลลัพท์ออกมา ซึ่งคำสั่งนี้จะคืนค่าผลลัพท์ออกมารายการแรกที่พบเท่านั้น
สำหรับส่วนของเมนูก็แทบไม่มีอะไรแตกต่างจากเดิมเช่นกัน ที่เปลี่ยนแปลงไปจะมีเพียงการอ่านข้อมูลเมนูจากฐานข้อมูลเท่านั้น
ที่ Index\Menu\Model จะทำการอ่านข้อมูลเมนูทั้งหมดออกมาจากฐานข้อมูล โดยมีลักษณะคล้ายการอ่านหน้าเพจนั่นแหละครับ ต่างกันแค่มีการอ่านข้อมูลด้วย execute() แทน ซึ่งคำสั่งนี้จะให้ผลลัพท์ออกมาได้หลายรายการ และจะส่งต่อผลลัพท์ไปวนลูปเพื่อจัดรูปแบบให้เหมาะสม ก่อนส่งข้อมูลเมนูกลับไปแสดงผล
namespace Index\Index;
use \Kotchasan\Http\Request;
use \Kotchasan\Template;
class Controller extends \Kotchasan\Controller
{
public function index(Request $request)
{
// รับค่าจาก $_GET['module'] ถ้าไม่มีการส่งค่ามาจะคืนค่า home โดยคืนค่าเป็น string ที่ตัวแปร module
// method filter() กำหนดให้รับค่าเฉพาะตัวอักษรที่กำหนดเท่านั้น
$module = $request->get('module', 'home')->filter('a-z');
// กำหนดค่า template ที่ใช้งานอยู่
Template::init(self::$cfg->skin);
// ชื่อคลาสจากโมดูล
$class = ucfirst($module).'\Index\Controller';
if (!method_exists($class, 'init')) {
// ไม่มีโมดูล เรียกหน้าเพจ
$class = 'Index\Main\Controller';
}
// โหลดโมดูล
$index = createClass($class)->init($request, $module);
// เริ่มต้นใช้งาน View
$view = new \Kotchasan\View;
// ใส่เนื้อหาลงใน View ตามที่กำหนดใน Template
$view->setContents(array(
// ข้อความจาก View แสดงบน title bar
'/{TITLE}/' => $index->title,
// เนื้อหาหน้า View ที่เรียกใช้งาน
'/{CONTENT}/' => $index->detail,
// แสดงเมนู
'/{TOPMENU}/' => \Kotchasan\Menu::render(\Index\Menu\Model::get(), $index->module),
// จำนวน Query
'/{QURIES}/' => \Kotchasan\Database\Driver::queryCount()
));
// โหลด template หลัก (index.html)
$template = Template::load('', '', 'index');
// ส่งออก HTML
echo $view->renderHTML($template);
}
}
บรรทัดแรก เราจะกำหนดให้หน้าเพจที่เราต้องการเป็นชื่อคลาสเลย เช่น มีการเรียกหน้า home จะได้ชื่อคลาสเป็น Home\Index\Controller และให้ตรวจสอบว่ามีคลาสนี้ติดตั้งไว้หรือไม่ ถ้ามีจะไปโหลดคลาสที่พบ ซึ่งปกติแล้วคลาสที่พบจะเป็นโมดูลอื่นๆ เช่น Chat\Index\Controller ซึ่งก็คือ Controller หลักของโมดูล Chat (modules/chat/controllers/index.php) ส่วนในกรณีที่ไม่พบโมดูล ก็อาจจะเป็นหน้าเพจ ซึ่งจะถูกส่งต่อไปยัง Index\Main\Controller ที่บรรทัดถัดไปแทน
ส่วน Index\Main\Controller ก็แทบไม่มีอะไรแตกต่างเช่นกัน มันยังคงทำหน้าที่คล้ายๆเวิร์คช้อปก่อนหน้า
namespace Index\Main;
use \Kotchasan\Http\Request;
use \Kotchasan\Template;
class Controller extends \Kotchasan\Controller
{
public function init(Request $request, $module)
{
// ตรวจสอบหน้าที่เรียกจากฐานข้อมูล
$page = \Index\Module\Model::get($module);
if ($page === false) {
$index = new \Index\Pagenotfound\View;
$title = $index->title();
$detail = $index->render();
} else {
$title = $page->topic;
$detail = $page->detail;
}
// เริ่มต้นใช้งาน View ของโมดูล Main
$view = new \Kotchasan\View;
// ใส่เนื้อหาลงใน View ตามที่กำหนดใน Template
$view->setContents(array(
// หัวข้อ
'/{TOPIC}/' => $title,
// เนื้อหา
'/{DETAIL}/' => $detail
));
// โหลด template หน้า main (main.html)
$template = Template::load('', '', 'main');
// คืนค่าข้อมูลโมดูล
return (object)array(
'module' => $module,
'title' => $title,
'detail' => $view->renderHTML($template)
);
}
}
โดยการนำค่าโมดูลที่ส่งมา ไปตรวจสอบกับฐานข้อมูล ที่ Index\Module\Model ถ้าพบก็คืนค่าข้อมูลหน้าที่พบ ถ้าไม่พบก็จะคืนค่าข้อมูลจาก Index\Pagenotfound\View แทน
ในเวิร์คช้อปนี้จะใช้การอ่านข้อมูลจากฐานข้อมูลนะครับ ดังนั้น ที่ Index\Module\Model ก็จะทำหน้าที่เชื่อมต่อกับฐานข้อมูลเพื่ออ่านข้อมูล $module ที่เลือก ออกมา
namespace Index\Module;
class Model extends \Kotchasan\Model
{
public static function get($module)
{
// เรียกใช้งาน Model
$model = new static;
// query ข้อมูลโมดูล (หน้าเพจ) ที่ต้องการ
$query = $model->db()->createQuery()
->from('site')
->where(array(
array('module', $module)
))
->cacheOn();
// คำสั่งสำหรับดู query
// SELECT * FROM `u`.`site` WHERE `module` = 'home' LIMIT 1
//echo $query->select()->limit(1)->text();
// คืนค่าข้อมูลที่พบ รายการเดียว
return $query->first();
}
}
อธิบายโค้ดเพิ่มเติมเป็นส่วนๆ นะครับ
// เรียกใช้งาน Model
$model = new static;
โค้ดด้านบนเป็นการเรียกใช้งาน Model ครับ คีย์เวิร์ด static เป็นคำสั่งของ PHP หมายถึงคลาสนี้ หรือก็คือ \kotchasan\Model นั่นเอง (เขียนได้อีกแบบว่า $model = new \Kotchasan\Model;)
// query ข้อมูลโมดูล (หน้าเพจ) ที่ต้องการ
$query = $model->db()->createQuery()
->from('site')
->where(array(
array('module', $module)
))
->cacheOn();
โค้ดด้านบนเป็นคำสั่งของ Model ใช้สำหรับสร้าง SQL Query มีชื่อว่า QueryBuilder (ถ้ามีการใช้คำสั่ง createQuery() แสดงว่ามีการใช้ QueryBuilder) รูปแบบของคำสั่งจะมีลักษณะคล้ายๆกับคำสั่งของ SQL ปกติ เพื่อให้จดจำได้ง่าย ซึ่งการดูผลลัพท์ SQL ของ Query ที่สร้างสามารถดูได้จากคำสั่ง text();
// คำสั่งสำหรับดู query
// SELECT * FROM `u`.`site` WHERE `module` = 'home' LIMIT 1
//echo $query->select()->limit(1)->text();
คำสั่งด้านบนคือตัวอย่างการใช้งาน text() พร้อมผลลัพท์ SQL ที่ได้
คำสั่ง cacheOn(); เป็นคำสั่งเปิดการใช้งานแคชของ QueryBuilder ถ้าต้องการเปิดใช้งานแคชจะต้องมีการดำเนินการดังนี้
- ต้องมีไดเร็คทอรี่ datas/cache/ และสามารถเขียนได้ ไฟล์แคชจะถูกเก็บไว้ในไดเร็คทอรี่นี้
- ต้องมีการตั้งค่าอายุของแคช (Kotchasan\Config::$cache_expire) เนื่องจากค่าเริ่มต้นระบุค่าเป็น 0 ไว้ ซึ่งจะหมายถึงไม่มีการใช้งานแคช หากต้องการใช้งานแคชจะต้องกำหนดค่านี้มากกว่า 0 เช่น 10 จะหมายถึงมีการแคชฐานข้อมูลเก็บไว้ 10 วินาที ค่านี้สามารถกำหนดใส่ไฟล์ settings/config.php ได้ (ไม่แนะนำให้แก้ไขไฟล์ของคชสารตรงๆ)
// คืนค่าข้อมูลที่พบ รายการเดียว
return $query->first();
สุดท้ายคือคำสั่ง query เอาผลลัพท์ออกมา ซึ่งคำสั่งนี้จะคืนค่าผลลัพท์ออกมารายการแรกที่พบเท่านั้น
สำหรับส่วนของเมนูก็แทบไม่มีอะไรแตกต่างจากเดิมเช่นกัน ที่เปลี่ยนแปลงไปจะมีเพียงการอ่านข้อมูลเมนูจากฐานข้อมูลเท่านั้น
namespace Index\Menu;
class Model extends \Kotchasan\Model
{
public static function get()
{
// เรียกใช้งาน Model
$model = new static;
// query ข้อมูลเมนูจากฐานข้อมูล
$query = $model->db()->createQuery()
->select()
->from('menu')
->order('order ASC')
->cacheOn();
// คำสั่งสำหรับดู query
// SELECT * FROM `u`.`menu` ORDER BY `order` ASC
//echo $query->text();
$result = array();
// query ข้อมูลและจัดรูปแบบเพื่อใส่ลงใน Array ตามข้อกำหนดของโมดูล
foreach ($query->execute() as $item) {
// จัดรูปแบบข้อมูลเมนูให้เหมาะสม สำหรับการสร้างเมนู
$result[$item->module] = array(
'text' => $item->text,
'target' => $item->target
);
if (empty($item->url)) {
$result[$item->module]['url'] = WEB_URL.'index.php?module='.$item->module;
} else {
$result[$item->module]['url'] = $item->url;
}
}
// คืนค่ารายการเมนูที่จัดรูปแบบแล้ว
return $result;
}
}
ที่ Index\Menu\Model จะทำการอ่านข้อมูลเมนูทั้งหมดออกมาจากฐานข้อมูล โดยมีลักษณะคล้ายการอ่านหน้าเพจนั่นแหละครับ ต่างกันแค่มีการอ่านข้อมูลด้วย execute() แทน ซึ่งคำสั่งนี้จะให้ผลลัพท์ออกมาได้หลายรายการ และจะส่งต่อผลลัพท์ไปวนลูปเพื่อจัดรูปแบบให้เหมาะสม ก่อนส่งข้อมูลเมนูกลับไปแสดงผล